package cn.zcltd.btg.set.core;

import cn.zcltd.btg.set.ParamGetter;
import cn.zcltd.btg.set.ParamType;
import cn.zcltd.btg.set.ToJson;
import cn.zcltd.btg.set.ToXml;
import cn.zcltd.btg.set.exception.BtgSetRuntimerException;
import cn.zcltd.btg.set.util.StringUtil;
import cn.zcltd.btg.sutil.EmptyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.*;

/**
 * 配置，包含配置所有模块、参数信息
 * 住：各配置的Module不支持重复id，各模块的Param不支持重复id
 */
public class Config implements ToJson, ToXml, ParamGetter {
    private static final Logger log = LoggerFactory.getLogger(Config.class);

    private String id; //配置id（必须）
    private String name; //配置name
    private String desc; //配置描述

    private Map<String, Module> moduleMap = new HashMap<>(); //配置模块map
    private List<Module> modules = new ArrayList<>(); //配置模块集合
    private Map<String, Param> paramMap = new HashMap<>(); //配置参数map（包含配置所有参数）
    private List<Param> params = new ArrayList<>(); //配置参数集合（包含配置根节点参数）

    public Config(String id, String name) {
        if (EmptyUtil.isEmpty(id)) {
            throw new BtgSetRuntimerException("id must not be null");
        }
        this.id = id;
        this.name = name;
    }

    /**
     * 创建一个config
     *
     * @param configId   config id
     * @param configName configName
     * @return 被创建的config
     */
    public static Config createConfig(String configId, String configName) {
        if (EmptyUtil.isEmpty(configId)) {
            throw new BtgSetRuntimerException("configId must not be null");
        }
        Config config = new Config(configId, configName);
        log.debug("create config:" + config.getId());
        return config;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        if (EmptyUtil.isEmpty(id)) {
            throw new BtgSetRuntimerException("id must not be null");
        }
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Map<String, Module> getModuleMap() {
        return moduleMap;
    }

    public void setModuleMap(Map<String, Module> moduleMap) {
        this.moduleMap = moduleMap;
    }

    public List<Module> getModules() {
        return modules;
    }

    public void setModules(List<Module> modules) {
        this.modules = modules;
    }

    public Map<String, Param> getParamMap() {
        return paramMap;
    }

    public void setParamMap(Map<String, Param> paramMap) {
        this.paramMap = paramMap;
    }

    public List<Param> getParams() {
        return params;
    }

    public void setParams(List<Param> params) {
        this.params = params;
    }

    /**
     * 为当前config创建一个param
     *
     * @param paramId    param id
     * @param paramName  param name
     * @param paramType  param type
     * @param paramValue param value
     * @return 被创建的param
     */
    public Param createParam(String paramId, String paramName, ParamType paramType, String paramValue) {
        Param param = new Param(paramId, paramName, paramType, paramValue, this);
        log.debug("create param:" + param.getPath());
        this.addParam(param);
        return param;
    }

    /**
     * 添加一个参数
     *
     * @param param 需要添加的参数
     */
    public void addParam(Param param) {
        if (null != this.paramMap.get(param.getId())) {
            log.warn("param exists:" + param.getPath());
            this.updateParam(param);
            return;
        }
        this.paramMap.put(param.getId(), param);
        this.params.add(param);
        log.debug("add param:" + param.getPath());
    }

    /**
     * 删除一个参数
     *
     * @param paramId 需要删除的参数id
     */
    public void deleteParam(String paramId) {
        if (null == this.paramMap.get(paramId)) {
            log.warn("param not exists:" + this.getId() + ">" + paramId);
            return;
        }
        this.paramMap.remove(paramId);
        Iterator<Param> iterator = this.params.iterator();
        while (iterator.hasNext()) {
            Param param = iterator.next();
            if (param.getId().equals(paramId)) {
                iterator.remove();
                break;
            }
        }
        log.debug("delete param:" + this.getId() + ">" + paramId);
    }

    /**
     * 更新一个参数
     *
     * @param param 需要更细的参数
     */
    public void updateParam(Param param) {
        Param paramOld = this.paramMap.get(param.getId());
        if (null == paramOld) {
            log.warn("param not exists:" + param.getPath());
            this.addParam(param);
            return;
        }
        if (paramOld.equals(param)) {
            log.debug("param no change:" + paramOld.getPath());
            return;
        }
        this.paramMap.put(param.getId(), param);
        for (int i = 0; i < this.params.size(); i++) {
            Param paramThis = params.get(i);
            if (paramThis.getId().equals(param.getId())) {
                this.params.remove(i);
                this.params.add(i, param);
                break;
            }
        }
        log.debug("update param:" + param.getPath());
    }

    /**
     * 获取一个参数
     *
     * @param paramId 参数id
     * @return 获取到的参数, 无参数返回null
     */
    public Param getParam(String paramId) {
        if (EmptyUtil.isEmpty(paramId)) {
            throw new BtgSetRuntimerException("paramId must not be null");
        }
        return this.paramMap.get(paramId.trim());
    }

    /**
     * 为当前config创建一个module
     *
     * @param moduleId   module id
     * @param moduleName module name
     * @return 被创建的module
     */
    public Module createModule(String moduleId, String moduleName) {
        Module module = new Module(moduleId, moduleName, this);
        log.debug("create module:" + module.getPath());
        this.addModule(module);
        return module;
    }

    /**
     * 添加一个模块
     *
     * @param module 需要添加的模块
     */
    public void addModule(Module module) {
        if (null != this.moduleMap.get(module.getId())) {
            log.warn("module exists:" + module.getPath());
            this.updateModule(module);
            return;
        }
        this.moduleMap.put(module.getId(), module);
        this.modules.add(module);
        log.debug("add module:" + module.getPath());
    }

    /**
     * 删除一个模块
     *
     * @param moduleId 需要删除的模块id
     */
    public void deleteModule(String moduleId) {
        Module moduleThis = this.moduleMap.get(moduleId);
        if (null == moduleThis) {
            log.warn("module not exists:" + this.getId() + ">" + moduleId);
            return;
        }
        this.moduleMap.remove(moduleId);
        Iterator<Module> iterator = this.modules.iterator();
        while (iterator.hasNext()) {
            Module module = iterator.next();
            if (module.getId().equals(moduleId)) {
                iterator.remove();
                break;
            }
        }

        //移除config中的param映射
        for (Param param : moduleThis.getParams()) {
            this.getParamMap().remove(param.getId());
        }
        log.debug("delete module:" + this.getId() + ">" + moduleId);
    }

    /**
     * 更新一个模块
     *
     * @param module 需要更细的模块
     */
    public void updateModule(Module module) {
        Module moduleOld = this.moduleMap.get(module.getId());
        if (null == moduleOld) {
            log.warn("module not exists:" + module.getPath());
            this.addModule(module);
            return;
        }
        if (moduleOld.equals(module)) {
            log.debug("module no change:" + moduleOld.getPath());
            return;
        }
        this.moduleMap.put(module.getId(), module);
        for (int i = 0; i < this.modules.size(); i++) {
            Module moduleThis = this.modules.get(i);
            if (moduleThis.getId().equals(module.getId())) {
                //移除config中的param映射
                for (Param param : moduleThis.getParams()) {
                    this.getParamMap().remove(param.getId());
                }
                this.modules.remove(i);//移除模块
                this.modules.add(i, module);
                break;
            }
        }
        log.debug("update module:" + module.getPath());
    }

    /**
     * 获取一个模块
     *
     * @param moduleId 模块id
     * @return 获取到的模块, 无参数返回null
     */
    public Module getModule(String moduleId) {
        if (EmptyUtil.isEmpty(moduleId)) {
            throw new BtgSetRuntimerException("moduleId must not be null");
        }
        return this.moduleMap.get(moduleId);
    }

    /**
     * 拷贝一个全新的config
     *
     * @return 全新的config
     */
    public Config copy() {
        Config configCopy = new Config(this.getId(), this.getName());

        Config config = this;

        //config
        configCopy.setDesc(config.getDesc());

        //config>param
        for (Param param : config.getParams()) {
            Param paramCopy = configCopy.createParam(param.getId(), param.getName(), param.getType(), param.getValue());
            paramCopy.setDesc(param.getDesc());
            paramCopy.setRemark(param.getRemark());
            paramCopy.setDatePattern(param.getDatePattern());

            paramCopy.setType(param.getType());
            paramCopy.setConfig(configCopy);
        }

        //config>module
        for (Module module : config.getModules()) {
            Module moduleCopy = configCopy.createModule(module.getId(), module.getName());
            moduleCopy.setDesc(module.getDesc());
            moduleCopy.setConfig(configCopy);

            //config>module>param
            for (Param param : module.getParams()) {
                Param paramCopy = moduleCopy.createParam(param.getId(), param.getName(), param.getType(), param.getValue());
                paramCopy.setDesc(param.getDesc());
                paramCopy.setRemark(param.getRemark());
                paramCopy.setDatePattern(param.getDatePattern());

                paramCopy.setType(param.getType());
                paramCopy.setModule(moduleCopy);
                paramCopy.setConfig(configCopy);
            }
        }

        return configCopy;
    }

    @Override
    public String toJson(boolean isFormat, int lv) {
        if (lv <= 0) {
            throw new BtgSetRuntimerException("lv must be greater than 1");
        }

        int tabNum = lv - 1;

        StringBuilder sb = new StringBuilder();

        if (isFormat) {
            sb.append(StringUtil.getTab(tabNum)).append("{");
            sb.append("\r\n");
            sb.append(StringUtil.getTab(tabNum + 1)).append("\"id\":\"").append(id).append("\"");
            sb.append(",\r\n");
            sb.append(StringUtil.getTab(tabNum + 1)).append("\"name\":\"").append(EmptyUtil.isNotEmpty(name) ? name : "").append("\"");
            sb.append(",\r\n");
            sb.append(StringUtil.getTab(tabNum + 1)).append("\"desc\":\"").append(EmptyUtil.isNotEmpty(desc) ? desc : "").append("\"");
            sb.append(",\r\n");

            //params
            StringBuilder paramSb = new StringBuilder();
            paramSb.append("[");
            paramSb.append("\r\n");
            StringBuilder configParamSb = new StringBuilder();
            for (Param param : this.params) {
                configParamSb.append(param.toJson(true, tabNum + 3)).append(",").append("\r\n");
            }
            if (configParamSb.length() > 0) {
                paramSb.append(configParamSb.substring(0, configParamSb.length() - 3));
            }
            paramSb.append("\r\n");
            paramSb.append(StringUtil.getTab(tabNum + 1)).append("]");
            sb.append(StringUtil.getTab(tabNum + 1)).append("\"params\":").append(paramSb);

            sb.append(",");
            sb.append("\r\n");

            //module
            StringBuilder moduleSb = new StringBuilder();
            moduleSb.append("[");
            moduleSb.append("\r\n");
            for (Module module : this.modules) {
                moduleSb.append(module.toJson(true, tabNum + 3));
            }
            moduleSb.append("\r\n");
            moduleSb.append(StringUtil.getTab(tabNum + 1)).append("]");
            sb.append(StringUtil.getTab(tabNum + 1)).append("\"modules\":").append(moduleSb);

            sb.append("\r\n");
            sb.append(StringUtil.getTab(tabNum)).append("}");
            return sb.toString();
        }

        sb.append("{");
        sb.append("\"id\":\"").append(id).append("\"");
        sb.append(",");
        sb.append("\"name\":\"").append(EmptyUtil.isNotEmpty(name) ? name : "").append("\"");
        sb.append(",");
        sb.append("\"desc\":\"").append(EmptyUtil.isNotEmpty(desc) ? desc : "").append("\"");
        sb.append(",");

        //params
        StringBuilder paramSb = new StringBuilder();
        paramSb.append("[");
        StringBuilder configParamSb = new StringBuilder();
        for (Param param : this.params) {
            configParamSb.append(param.toJson(false, 1)).append(",");
        }
        if (configParamSb.length() > 0) {
            paramSb.append(configParamSb.substring(0, configParamSb.length() - 1));
        }
        paramSb.append("]");
        sb.append("\"params\":").append(paramSb);

        sb.append(",");

        //module
        StringBuilder moduleSb = new StringBuilder();
        moduleSb.append("[");
        for (Module module : this.modules) {
            moduleSb.append(module.toJson(false, 1));
        }
        moduleSb.append("]");
        sb.append("\"modules\":").append(moduleSb);

        sb.append("}");
        return sb.toString();
    }

    @Override
    public String toJson() {
        return toJson(true, 1);
    }

    @Override
    public String toXml(boolean isFormat, int lv) {
        if (lv <= 0) {
            throw new BtgSetRuntimerException("lv must be greater than 1");
        }

        int tabNum = lv - 1;

        StringBuilder sb = new StringBuilder();

        if (isFormat) {
            sb.append(StringUtil.getTab(tabNum)).append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            sb.append("\r\n");
            sb.append(StringUtil.getTab(tabNum)).append("<config");
            sb.append(" id=\"").append(id).append("\"");
            if (EmptyUtil.isNotEmpty(name)) {
                sb.append(" name=\"").append(name).append("\"");
            }
            if (EmptyUtil.isNotEmpty(desc)) {
                sb.append(" desc=\"").append(desc).append("\"");
            }
            sb.append(">");

            //param
            for (Param param : this.params) {
                sb.append("\r\n    ");
                sb.append(param.toXml(true, tabNum + 1));
            }

            //module
            for (Module module : this.modules) {
                sb.append("\r\n");
                sb.append(module.toXml(true, tabNum + 2));
            }

            sb.append("\r\n");
            sb.append(StringUtil.getTab(tabNum)).append("</config>");
            return sb.toString();
        }

        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        sb.append("<config");
        sb.append(" id=\"").append(id).append("\"");
        if (EmptyUtil.isNotEmpty(name)) {
            sb.append(" name=\"").append(name).append("\"");
        }
        if (EmptyUtil.isNotEmpty(desc)) {
            sb.append(" desc=\"").append(desc).append("\"");
        }
        sb.append(">");

        //param
        for (Param param : this.params) {
            sb.append(param.toXml(false, 1));
        }

        //module
        for (Module module : this.modules) {
            sb.append(module.toXml(false, 1));
        }

        sb.append("</config>");
        return sb.toString();
    }

    @Override
    public String toXml() {
        return toXml(true, 1);
    }

    @Override
    public String toString() {
        return this.toJson();
    }

    public boolean equals(Config target) {
        return this.toString().equals(target.toString());
    }

    @Override
    public String getString(String key) {
        return getString(key, null);
    }

    @Override
    public String getString(String key, String defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getString(param, defaultValue);
    }

    @Override
    public Boolean getBoolean(String key) {
        return getBoolean(key, null);
    }

    @Override
    public Boolean getBoolean(String key, Boolean defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getBoolean(param, defaultValue);
    }

    @Override
    public Number getNumber(String key) {
        return getNumber(key, null);
    }

    @Override
    public Number getNumber(String key, Number defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getNumber(param, defaultValue);
    }

    @Override
    public Integer getInteger(String key) {
        return getInteger(key, null);
    }

    @Override
    public Integer getInteger(String key, Integer defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getInteger(param, defaultValue);
    }

    @Override
    public Long getLong(String key) {
        return getLong(key, null);
    }

    @Override
    public Long getLong(String key, Long defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getLong(param, defaultValue);
    }

    @Override
    public Float getFloat(String key) {
        return getFloat(key, null);
    }

    @Override
    public Float getFloat(String key, Float defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getFloat(param, defaultValue);
    }

    @Override
    public Double getDouble(String key) {
        return getDouble(key, null);
    }

    @Override
    public Double getDouble(String key, Double defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getDouble(param, defaultValue);
    }

    @Override
    public BigDecimal getBigDecimal(String key) {
        return getBigDecimal(key, null);
    }

    @Override
    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getBigDecimal(param, defaultValue);
    }

    @Override
    public Date getDate(String key) {
        return getDate(key, "");
    }

    @Override
    public Date getDate(String key, Date defaultValue) {
        return getDate(key, "", defaultValue);
    }

    @Override
    public Date getDate(String key, String datePattern) {
        return getDate(key, datePattern, null);
    }

    @Override
    public Date getDate(String key, String datePattern, Date defaultValue) {
        Param param = this.getParam(key);
        return ParamConvert.getDate(param, key, defaultValue);
    }
}